#!/bin/sh
#
#  Root-RO for Raspian
#  - http://www.josepsanz.net
#  - http://www.saltos.org
#
#  Based on the work of:
#  - https://gist.github.com/niun/34c945d70753fc9e2cc7
#  - https://github.com/chesty/overlayroot
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see
#    <http://www.gnu.org/licenses/>.
#

# no pre requirement
PREREQ=""

prereqs()
{
    echo "${PREREQ}"
}

case "$1" in
    prereqs)
    prereqs
    exit 0
    ;;
esac

# import /usr/share/initramfs-tools/scripts/functions
. /scripts/functions

# ${ROOT} and ${rootmnt} are predefined by caller of this script. Note that
# the root fs ${rootmnt} it mounted readonly on the initrams, which fits nicely
# for our purposes.

modprobe -qb overlay
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 1: missing kernel module overlay"
    exit 0
fi

modprobe -qb fuse
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 2: missing kernel module fuse"
    exit 0
fi

# make the mount point on the init root fs /mnt/root-ro
[ -d /mnt/root-ro ] || mkdir -p /mnt/root-ro
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 3: failed to create /mnt/root-ro"
    exit 0
fi

# make the mount point on the init root fs /mnt/root-rw
[ -d /mnt/root-rw ] || mkdir -p /mnt/root-rw
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 4: failed to create /mnt/root-rw"
    exit 0
fi

# mount a tmpfs at /mnt/root-rw
mount -t tmpfs tmpfs /mnt/root-rw
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 5: failed to create tmpfs"
    exit 0
fi

[ -d /mnt/root-rw/upper ] || mkdir -p /mnt/root-rw/upper
if [ $? -ne 0 ]; then
	log_failure_msg "ERROR 6: failed to create /mnt/root-rw/upper"
	exit 0
fi

[ -d /mnt/root-rw/work ] || mkdir -p /mnt/root-rw/work
if [ $? -ne 0 ]; then
	log_failure_msg "ERROR 7: failed to create /mnt/root-rw/work"
	exit 0
fi

# make the mount point on the init boot fs /mnt/boot-ro
[ -d /mnt/boot-ro ] || mkdir -p /mnt/boot-ro
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 8: failed to create /mnt/boot-ro"
    exit 0
fi

# make the mount point on the init boot fs /mnt/boot-ro2
[ -d /mnt/boot-ro2 ] || mkdir -p /mnt/boot-ro2
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 9: failed to create /mnt/boot-ro2"
    exit 0
fi

# make the mount point on the init boot fs /mnt/boot-rw
[ -d /mnt/boot-rw ] || mkdir -p /mnt/boot-rw
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 10: failed to create /mnt/boot-rw"
    exit 0
fi

# mount a tmpfs at /mnt/boot-rw
mount -t tmpfs tmpfs /mnt/boot-rw
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 11: failed to create tmpfs"
    exit 0
fi

[ -d /mnt/boot-rw/upper ] || mkdir -p /mnt/boot-rw/upper
if [ $? -ne 0 ]; then
	log_failure_msg "ERROR 12: failed to create /mnt/boot-rw/upper"
	exit 0
fi

[ -d /mnt/boot-rw/work ] || mkdir -p /mnt/boot-rw/work
if [ $? -ne 0 ]; then
	log_failure_msg "ERROR 13: failed to create /mnt/boot-rw/work"
	exit 0
fi

# root is mounted on ${rootmnt}, move it to /mnt/root-ro.
mount -o move ${rootmnt} /mnt/root-ro
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 14: failed to move root away from ${rootmnt} to /mnt/root-ro"
    exit 0
fi

# mount boot device to /mnt/boot-ro
BOOTDEV=$(cat /mnt/root-ro/etc/fstab | awk '{if($2=="/boot")print $1}')
# check BOOTDEV begins with PARTUUID=
case "${BOOTDEV}" in
    PARTUUID=*)
        PARTUUID=`echo ${BOOTDEV} | cut -d= -f2`
        # change BOOTDEV to be /dev/...
        BOOTDEV=`blkid | grep ${PARTUUID} | cut -d: -f1`;;
esac
mount -o ro $BOOTDEV /mnt/boot-ro
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 15: failed to mount ${BOOTDEV} to /mnt/boot-ro"
    exit 0
fi

# bindfs /mnt/boot-ro to /mnt/boot-ro2
bindfs /mnt/boot-ro /mnt/boot-ro2
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 16: failed to bindfs /mnt/boot-ro to /mnt/boot-ro2"
    exit 0
fi

# there is nothing left at ${rootmnt} now. So for any error we get we should
# either do recovery to restore ${rootmnt} for drop to a initramfs shell using
# "panic". Otherwise the boot process is very likely to fail with even more
# errors and leave the system in a wired state.

# mount virtual fs ${rootmnt} with rw-fs /mnt/root-rw on top or ro-fs /mnt/root-ro.
mount -t overlay -o lowerdir=/mnt/root-ro,upperdir=/mnt/root-rw/upper,workdir=/mnt/root-rw/work overlay ${rootmnt}
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 17: failed to create new ro/rw layer on ${rootmnt}"
    # do recovery and try resoring the mount for ${rootmnt}
    mount -o move /mnt/root-ro ${rootmnt}
    if [ $? -ne 0 ]; then
       # thats bad, drop to shell to let the user try fixing this
       panic "ERROR 18: failed to move /mnt/root-ro back to ${rootmnt}"
    fi
    exit 0
fi

# mount virtual fs for boot directory
mount -t overlay -o lowerdir=/mnt/boot-ro2,upperdir=/mnt/boot-rw/upper,workdir=/mnt/boot-rw/work overlay ${rootmnt}/boot
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 19: failed to create new ro/rw layer on ${rootmnt}/boot"
    exit 0
fi

# now the real root fs is on /mnt/root-ro of the init file system, our layered
# root fs is set up at ${rootmnt}. So we can write anywhere in {rootmnt} and the
# changes will end up in /mnt/root-rw while /mnt/root-ro it not touched. However
# /mnt/root-ro and /mnt/root-rw are on the initramfs root fs, which will be removed
# an replaced by ${rootmnt}. Thus we must move /mnt/root-ro and /mnt/root-rw to the
# rootfs visible later, ie. ${rootmnt}/mnt/root-ro and ${rootmnt}/mnt/root-rw.
# Since the layered ro/rw is already up, these changes also end up on
# /mnt/root-rw while /mnt/root-ro is not touched.

# move mount from /mnt/root-ro to ${rootmnt}/mnt/root-ro
[ -d ${rootmnt}/mnt/root-ro ] || mkdir -p ${rootmnt}/mnt/root-ro
mount -o move /mnt/root-ro ${rootmnt}/mnt/root-ro
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 20: failed to move /mnt/root-ro to ${rootmnt}/mnt/root-ro"
    exit 0
fi

# move mount from /mnt/root-rw to ${rootmnt}/mnt/root-rw
[ -d ${rootmnt}/mnt/root-rw ] || mkdir -p ${rootmnt}/mnt/root-rw
mount -o move /mnt/root-rw ${rootmnt}/mnt/root-rw
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 21: failed to move /mnt/root-rw to ${rootmnt}/mnt/root-rw"
    exit 0
fi

# move mount from /mnt/boot-ro to ${rootmnt}/mnt/boot-ro
[ -d ${rootmnt}/mnt/boot-ro ] || mkdir -p ${rootmnt}/mnt/boot-ro
mount -o move /mnt/boot-ro ${rootmnt}/mnt/boot-ro
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 22: failed to move /mnt/boot-ro to ${rootmnt}/mnt/boot-ro"
    exit 0
fi

# move mount from /mnt/boot-ro2 to ${rootmnt}/mnt/boot-ro2
[ -d ${rootmnt}/mnt/boot-ro2 ] || mkdir -p ${rootmnt}/mnt/boot-ro2
mount -o move /mnt/boot-ro2 ${rootmnt}/mnt/boot-ro2
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 23: failed to move /mnt/boot-ro2 to ${rootmnt}/mnt/boot-ro2"
    exit 0
fi

# move mount from /mnt/boot-rw to ${rootmnt}/mnt/boot-rw
[ -d ${rootmnt}/mnt/boot-rw ] || mkdir -p ${rootmnt}/mnt/boot-rw
mount -o move /mnt/boot-rw ${rootmnt}/mnt/boot-rw
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 24: failed to move /mnt/boot-rw to ${rootmnt}/mnt/boot-rw"
    exit 0
fi

# technically, everything is set up nicely now. Since ${rootmnt} had beend
# mounted read-only on the initfamfs already, ${rootmnt}/mnt/root-ro is it, too.
# Now we init process could run - but unfortunately, we may have to prepare
# some more things here.
# Basically, there are two ways to deal with the read-only root fs. If the
# system is made aware of this, things can be simplified a lot.
# If it is not, things need to be done to our best knowledge.
#
# So we assume here, the system does not really know about our read-only root fs.
#
# Let's deal with /etc/fstab first. It usually contains an entry for the root
# fs, which is no longer valid now. We have to remove it and add our new
# /mnt/root-ro entry.
# Remember we are still on the initramfs root fs here, so we have to work on
# ${rootmnt}/etc/fstab. The original fstab is ${rootmnt}/mnt/root-ro/etc/fstab.
ROOT_TYPE=$(cat /proc/mounts | grep ${ROOT} | cut -d' ' -f3)
ROOT_OPTIONS=$(cat /proc/mounts | grep ${ROOT} | cut -d' ' -f4)
BOOT_TYPE=$(cat /proc/mounts | grep ${BOOTDEV} | cut -d' ' -f3)
BOOT_OPTIONS=$(cat /proc/mounts | grep ${BOOTDEV} | cut -d' ' -f4)
cat <<EOF >${rootmnt}/etc/fstab
#
#  This fstab is in RAM, the real one can be found at /mnt/root-ro/etc/fstab
#  The original entry for '/' and all swap files have been removed.  The new
#  entry for the read-only the real root fs follows. Write access can be
#  enabled using:
#    sudo mount -o remount,rw /mnt/root-ro
#  re-mounting it read-only is done using:
#    sudo mount -o remount,ro /mnt/root-ro
#
${BOOTDEV} /mnt/boot-ro ${BOOT_TYPE} ${BOOT_OPTIONS} 0 0
${ROOT} /mnt/root-ro ${ROOT_TYPE} ${ROOT_OPTIONS} 0 0
#
#  remaining entries from the original /mnt/root-ro/etc/fstab follow.
#
EOF
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 25: failed to modify /etc/fstab (step 1)"
    #exit 0
fi

#remove root, boot and swap from fstab
cat ${rootmnt}/mnt/root-ro/etc/fstab | awk '{if($2!="/"&&$2!="/boot"&&$2!="swap")print $0}' >> ${rootmnt}/etc/fstab
if [ $? -ne 0 ]; then
    log_failure_msg "ERROR 26: failed to modify etc/fstab (step 2)"
    #exit 0
fi

# now we are done. Additinal steps may be necessary depending on the actualy
# distribution and/or its configuration.

log_success_msg "Sucessfully set up ro/rw/tmpfs layer for root and boot fs using overlay"

exit 0
